#ifndef I_LIB_IP_I_TILE_H
#define I_LIB_IP_I_TILE_H

#include <cassert>

namespace idl
{
    template<typename T>
    class TileImage/*a sub-image region*/
    {
    public:
        typedef       T&       reference;
        typedef const T& const_reference;
    
        TileImage()
        {
            _x = _y = -1;
            _w = _h = _pad = _rowoffset = 0;
            _data = NULL;
        }
    
        TileImage(int x, 
            /*starting pixel coordinate in the original image coordinate space*/
            int y,
            int w, 
            /*tile width and height*/
            int h,
            /*indicates the nearest distance to memory boundary*/
            int pad,
            /*src image's width*/
            int row_offset,
            /*note this is the src image's starting address*/
            T* image) : 
            _x(x), _y(y), _w(w), _h(h), _pad(pad), _rowoffset(row_offset)
        {
            assert(image != NULL);
            _data = image + (y*row_offset + x);
        };

        TileImage(const TileImage<T>& tile)
        {
            _x = tile.x();
            _y = tile.y();
            _w = tile.width();
            _h = tile.height();
            _pad = tile.pad();
            _rowoffset = tile.row_offset();
            _data = tile.data();
        };

        TileImage& operator=(const TileImage<T>& tile)
        {
            if (this != &tile)
            {
                this->_x = tile.x();
                this->_y = tile.y();
                this->_w = tile.width();
                this->_h = tile.height();
                this->_pad = tile.pad();
                this->_rowoffset = tile.row_offset();
                this->_data = tile.data();
            }
            return (*this);
        };
    
    virtual ~TileImage(){};

    void create(T *image, int x, int y, int w, int h, int pad, int row_offset)
    {
        _x = x;
        _y = y;
        _w = w;
        _h = h;
        _pad = pad;
        _rowoffset = row_offset;
        _data = image + (y*row_offset + x);
    }

    int x() const { return _x; }
    int y() const { return _y; }
    int width()  const { return _w; }
    int height() const { return _h; }
    int pad()    const { return _pad; }
    int row_offset() const { return _rowoffset; }
    
    //data access routines
    T* data() 
    { 
        return _data; 
    };
    T* data() const 
    { 
        return _data; 
    }
    const T* const_data() const 
    { 
        return _data;
    };
   
    T* row(int r) const 
    { 
        assert(r >= 0 && r < _h); 
        return (_data + r*_rowoffset);
    };
    
    const T* const_row(int r) const 
    { 
        assert(r >= 0 && r < _h); 
        return (_data + r*_rowoffset);
    };

    T* operator[](int r) 
    { 
        assert(r >= 0 && r < _h); 
        return (_data + r*_rowoffset);
    }
    
    const T* operator[](int r) const 
    { 
        assert(r >= 0 && r < _h); 
        return (_data + r*_rowoffset);
    }

    reference operator()(int r, int c) 
    { 
        assert(r >= 0 && r < _h); 
        assert(c >= 0 && c < _w); 
        return (_data[r*_rowoffset+c]);
    }
    
    const_reference operator()(int r, int c) const 
    { 
        assert(r >= 0 && r < _h); 
        assert(c >= 0 && c < _w); 
        return (_data[r*_rowoffset + c]);
    }

    protected:
        T* _data; /*starting address*/
        int _w, _h, _pad, _rowoffset, _x, _y;
    };

    template<typename T, unsigned int m, unsigned int n, unsigned int pad>
    class MultiTileImage
    {
    public:
        typedef       TileImage<T>&       reference;
        typedef const TileImage<T>& const_reference;
        static const unsigned int DIM = m*n;
        
        MultiTileImage(T* image, int width, int height, const int roi[4]);
        MultiTileImage(T* image, int width, int height);
        MultiTileImage(int width, int height);
        MultiTileImage();
        
        MultiTileImage(const MultiTileImage<T, m, n, pad>& mti)
        {
            //_data = mti.data();
            _width = mti.width();
            _height = mti.height();
            for (unsigned int i = 0; i < mti.nr_tiles(); ++i)
            {
                _tiles[i] = mti[i];
            }
        };

        MultiTileImage& operator=(const MultiTileImage<T, m, n, pad>& mti)
        {
            if (this != &mti)
            {
                this->_data = mti.data();
                this->_width = mti.width();
                this->_height = mti.height();
                for (unsigned int i = 0; i < mti.nr_tiles(); ++i)
                {
                    this->_tiles[i] = mti[i];
                }
            }
            return(*this);
        };

        virtual ~MultiTileImage(){};
        
        static unsigned int nr_rows(){ return m; }
        static unsigned int nr_cols(){ return n; }
        static unsigned int nr_tiles(){ return DIM; }/*# of tiles in this multi-tile image*/
        
        int width()  const { return _width; }
        int height() const { return _height; }
        
        void wrap(T* image, int width, int height);
        void wrap(T* image, int width, int height, const int roi[4]);
        
        //data access routines
        T* data() 
        { 
            return _data; 
        };
        T* data() const 
        { 
            return _data;
        }
        const T* const_data() const 
        { 
            return _data;
        };
        reference operator[](int i)
        { 
            assert(i >= 0 && i < (int)DIM);
            return _tiles[i]; 
        }
        const_reference operator[](int i) const 
        { 
            assert(i >= 0 && i < (int)DIM); 
            return _tiles[i];
        }
    protected:
        T* _data; /*external image starting address, continuous memory block*/
        int _width, _height; /*the image's dimension*/
        TileImage<T> _tiles[DIM];
    };

    template<typename T, unsigned int m, unsigned int n, unsigned int pad>
    MultiTileImage<T, m, n, pad>::MultiTileImage() :_data(NULL), _width(0), _height(0){}
    
    template<typename T, unsigned int m, unsigned int n, unsigned int pad>
    MultiTileImage<T, m, n, pad>::MultiTileImage(int width, int height) : 
        _data(NULL), _width(width), _height(height){}
    
    template<typename T, unsigned int m, unsigned int n, unsigned int pad>
    MultiTileImage<T, m, n, pad>::MultiTileImage(T* image, int width, int height)
    {
        wrap(image, width, height);
    }

    template<typename T, unsigned int m, unsigned int n, unsigned int pad>
    MultiTileImage<T, m, n, pad>::MultiTileImage(T* image, int width, int height, const int roi[4])
    {
        if (!roi)
        {
            wrap(image, width, height);
        }
        else
        {
            wrap(image, width, height, roi);
        }
    }

    template<typename T, unsigned int m, unsigned int n, unsigned int pad>
    void MultiTileImage<T, m, n, pad>::wrap(T* image, int width, int height)
    {
        /*assign member parameters*/
        assert(image != NULL && width > pad && height > pad);
        _data = image;
        _width = width;
        _height = height;
        unsigned int i, j, k = 0;
        int pad2 = (int)(pad << 1);
        assert(height > pad2 && width > pad2);
        
        unsigned int th = (unsigned int)height / m;
        unsigned int tw = (unsigned int)width / n;
        
        assert(th > pad && tw > pad);/*note pad can NOT be larger than tw or th*/
        
        unsigned int x = 0, y = 0;
        int twmpad = n > 1 ? (int)(tw - pad) : (int)(tw - pad2);
        int thmpad = m > 1 ? (int)(th - pad) : (int)(th - pad2);
        
        /*1st row:*/
        _tiles[k].create(_data, (int)pad, (int)pad, twmpad, thmpad, (int)pad, _width); 
        k++;
        for (x = tw, j = 1; j < n - 1; j++, x += tw)
        {
            _tiles[k].create(_data, (int)x, (int)pad, (int)tw, thmpad, (int)pad, _width); 
            k++;
        }
        if (n > 1)
        {
            _tiles[k].create(_data, (int)x, (int)pad, twmpad, thmpad, (int)pad, _width); 
            k++;
        }
        
        for (y = th, i = 1; i < m - 1; i++, y += th)
        {
            _tiles[k].create(_data, (int)pad, (int)y, twmpad, (int)th, (int)pad, _width); 
            k++;
            for (x = tw, j = 1; j < n - 1; j++, x += tw)
            {
                _tiles[k].create(_data, (int)x, (int)y, (int)tw, (int)th, (int)pad, _width); 
                k++;
            }
            if (n > 1)
            {
                _tiles[k].create(_data, (int)x, (int)y, twmpad, (int)th, (int)pad, _width); 
                k++;
            }
        }
        
        /*last row:*/
        if (m > 1)
        {
            _tiles[k].create(_data, (int)pad, (int)y, twmpad, thmpad, (int)pad, _width); 
            k++;
            for (x = tw, j = 1; j < n - 1; j++, x += tw)
            {
                _tiles[k].create(_data, (int)x, (int)y, (int)tw, thmpad, (int)pad, _width); 
                k++;
            }
            if (n > 1)
            {
                _tiles[k].create(_data, (int)x, (int)y, twmpad, thmpad, (int)pad, _width);
            }
        }
    }

    template<typename T, unsigned int m, unsigned int n, unsigned int pad>
    void MultiTileImage<T, m, n, pad>::wrap(T* image, int width, int height, const int roi[4])
    {
        /*assign member parameters*/
        assert(image != NULL && width > pad && height > pad && roi != NULL);
        _data = image;
        _width = width;
        _height = height;
        
        int orig_x = i_max(roi[0], (int)pad);
        int orig_y = i_max(roi[1], (int)pad);
        int end_x = i_min((roi[0] + roi[2]), (_width - (int)pad));
        int end_y = i_min((roi[1] + roi[3]), (_height - (int)pad));
        int x, y;
        int roi_width = end_x - orig_x;
        int roi_height = end_y - orig_y;
    
        assert(orig_x >= 0
            && orig_y >= 0
            && roi_width > 0
            && roi_height > 0
            && (orig_x + roi_width) <= width
            && (orig_y + roi_height) <= height);
        
        unsigned int th = (unsigned int)roi_height / m;
        unsigned int tw = (unsigned int)roi_width / n;
    
        assert(th > pad && tw > pad); /*note pad can NOT be larger than tw or th*/
        
        unsigned int i, j, k = 0;
        
        for (y = orig_y, i = 0; i < m; ++i, y += (int)th)
        {
            for (x = orig_x, j = 0; j < n; ++j, x += (int)tw)
            {
                _tiles[k].create(_data, x, y, (int)tw, (int)th, (int)pad, _width); 
                k++;
            }
        }
    }
}/*namespace idl*/

#endif